using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace PBR
{
    /// <summary>
    /// Class that contains the Previewer Control, which is contianed in a TabPage
    /// This control allows real-time adjustments of the vector field etc.
    /// </summary>
    public partial class PreviewerControl : UserControl
    {
        private enum VectorTool { Select, Add, Remove };
        private enum StrokeMode { RotateR, RotateL, Translate, None, Select };

        #region Static Variables

        private const int DISTANCE = 10;
        private const float LINEWIDTH = 1.0f;
        private const float LINELENGTH = 3.0f;
        private const float OUTERCIRCLERADIUS = 4.0f;
        private const float INNERCIRCLERADIUS = 2.0f;

        /// <summary>
        /// Length of user created vectors
        /// </summary>
        private const int VECTORLENGTH = 15;

        /// <summary>
        /// After IDLETIMEOUT ms do a ComputeVectors if it is dirty
        /// </summary>
        private const int IDLETIMEOUT = 1000;
       
        #endregion

        #region Member Variables

        private Scene scene;
        private MainForm mainForm;
        private Bitmap vectorBmp;

        private Pen rectanglePen;
        private Point mouseDownPt;
        private Rectangle highlightRectangleSm;
        private Rectangle highlightRectangleFl;
        private Rectangle colorPanelRect;
        private Point mouseDownHighlightPt;
        private bool isDragging = false;

        private int distance = DISTANCE;
        private float lineWidth = LINEWIDTH;
        private float lineLength = LINELENGTH;
        private float outerCircleRadius = OUTERCIRCLERADIUS;
        private float innerCircleRadius = INNERCIRCLERADIUS;
        private Pen unselectedPen = new Pen(Color.Cyan, 0.5f);
        private Pen selectedPen = new Pen(Color.GreenYellow, 1.0f);
        private Pen vectorPen = new Pen(Color.YellowGreen, 1.0f);
        private VectorTool vectorTool = VectorTool.Add;
        private StrokeMode strokeMode = StrokeMode.RotateR;
        private byte[,] vectorGrid;
        int vectorGridDim;
        private bool showBorders = false;
        private Bitmap paintPreview;
        private Rectangle colorLocatorRect;

        private int idleTime = 0;

        private bool vectorsDirty = false;

        #endregion

        #region Member Functions

        /// <summary>
        /// Constructor for the Previewer Control
        /// </summary>
        /// <param name="scene">Holds global scene data</param>
        public PreviewerControl(Scene scene, MainForm mainForm)
        {
            InitializeComponent();

            // Update Tab Items
            this.Text = "Previewer";

            // Scene Items
            this.scene = scene;
            this.mainForm = mainForm;

            // Images
            Bitmap colorBmp = scene.Painting.ActiveLayer.ColorImage.Bitmap;
            vectorBmp = new Bitmap(vectorPanel.Width, vectorPanel.Height);
            paintPreview = new Bitmap(paintPanel.Width, paintPanel.Height, PixelFormat.Format32bppArgb);

            // Vector Items
            vectorGridDim = vectorPanel.Width / distance + 1;
            vectorGrid = new byte[vectorGridDim, vectorGridDim];

            // Locator Items
            rectanglePen = new Pen(new SolidBrush(Color.Blue));

            if (colorBmp.Width > colorBmp.Height)
            {
                int adjustedHeight = locatorPanel.Height * colorBmp.Height / colorBmp.Width;
                colorLocatorRect = new Rectangle(0, (locatorPanel.Height - adjustedHeight) / 2,
                    locatorPanel.Width, adjustedHeight);
            }
            else
            {
                int adjustedWidth = locatorPanel.Width * colorBmp.Width / colorBmp.Height;
                colorLocatorRect = new Rectangle((locatorPanel.Width - adjustedWidth) / 2, 0,
                    adjustedWidth, locatorPanel.Height);
            }
            highlightRectangleSm = new Rectangle(colorLocatorRect.X, colorLocatorRect.Y,
                locatorPanel.Width * locatorPanel.Width / Math.Max(colorBmp.Width, colorBmp.Height),
                locatorPanel.Height * locatorPanel.Height / Math.Max(colorBmp.Width, colorBmp.Height));
            highlightRectangleFl = new Rectangle(0, 0, colorPanel.Width, colorPanel.Height);
            colorPanelRect = new Rectangle(new Point(0, 0), colorPanel.Size);
            showBordersBtn.Checked = showBorders;

            idleTimer.Enabled = true;

            colorPanel.Invalidate();
            vectorPanel.Invalidate();
        }

        public void NewImage()
        {
            Bitmap colorBmp = scene.Painting.ActiveLayer.ColorImage.Bitmap;
            if (colorBmp.Width > colorBmp.Height)
            {
                int adjustedHeight = locatorPanel.Height * colorBmp.Height / colorBmp.Width;
                colorLocatorRect = new Rectangle(0, (locatorPanel.Height - adjustedHeight) / 2,
                    locatorPanel.Width, adjustedHeight);
            }
            else
            {
                int adjustedWidth = locatorPanel.Width * colorBmp.Width / colorBmp.Height;
                colorLocatorRect = new Rectangle((locatorPanel.Width - adjustedWidth) / 2, 0,
                    adjustedWidth, locatorPanel.Height);
            }
            highlightRectangleSm = new Rectangle(colorLocatorRect.X, colorLocatorRect.Y,
                locatorPanel.Width * locatorPanel.Width / Math.Max(colorBmp.Width, colorBmp.Height),
                locatorPanel.Height * locatorPanel.Height / Math.Max(colorBmp.Width, colorBmp.Height));
            showBordersBtn.Checked = showBorders;
        }

        #endregion
        
        // Panel with 'Finder' rectangle
        #region Locator Panel

        private void locatorPanel_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            Bitmap colorBmp = scene.Painting.ActiveLayer.ColorImage.Bitmap;
            g.DrawImage(scene.Painting.ActiveLayer.ColorImage.Bitmap, colorLocatorRect);
            g.DrawRectangle(rectanglePen, highlightRectangleSm);
            g.Flush();
        }

        private void locatorPanel_MouseDown(object sender, MouseEventArgs e)
        {
            mouseDownPt = new Point(e.X, e.Y);
            mouseDownHighlightPt = new Point(highlightRectangleSm.X, highlightRectangleSm.Y);
            isDragging = true;
        }

        /// <summary>
        /// View has changed - so refresh all views
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void locatorPanel_MouseUp(object sender, MouseEventArgs e)
        {
            isDragging = false;
            colorPanel.Invalidate();
            ComputeVectorField();
            vectorPanel.Invalidate();
            PaintPreview();
            paintPanel.Invalidate();
        }

        private void locatorPanel_MouseMove(object sender, MouseEventArgs e)
        {
            Bitmap colorBmp = scene.Painting.ActiveLayer.ColorImage.Bitmap;

            if (isDragging)
            {
                Point newTopPt = new Point(e.X - mouseDownPt.X + mouseDownHighlightPt.X,
                        e.Y - mouseDownPt.Y + mouseDownHighlightPt.Y);
                Rectangle newRect = new Rectangle(newTopPt, highlightRectangleSm.Size);

                if (colorLocatorRect.Contains(newRect))
                {
                    Size fullSize = new Size(colorPanel.Width, colorPanel.Height);


                    Point fullPoint = new Point((newTopPt.X - colorLocatorRect.X) * (colorBmp.Width / colorLocatorRect.Width),
                        (newTopPt.Y - colorLocatorRect.Y) * (colorBmp.Height / colorLocatorRect.Height));
                    Rectangle invalidRect = new Rectangle(
                        Math.Min(newTopPt.X, highlightRectangleSm.X),
                        Math.Min(newTopPt.Y, highlightRectangleSm.Y),
                        Math.Abs(newTopPt.X - highlightRectangleSm.X) + highlightRectangleSm.Width + 2,
                        Math.Abs(newTopPt.Y - highlightRectangleSm.Y) + highlightRectangleSm.Height + 2);
                    highlightRectangleFl = new Rectangle(fullPoint, fullSize);                    
                    highlightRectangleSm = newRect;
                    locatorPanel.Invalidate(invalidRect);
                }
            }
        }

        #endregion

        // Panel with a preview of the source image
        #region Color Panel
        
        private void colorPanel_Paint(object sender, PaintEventArgs e)
        {
            Bitmap colorBmp = scene.Painting.ActiveLayer.ColorImage.Bitmap;
            Graphics g = e.Graphics;
            g.DrawImage(colorBmp, colorPanelRect, highlightRectangleFl, GraphicsUnit.Pixel);
            g.Flush();
        }

        #endregion

        // The Vector Panel actually computes the vectors itself, instead of
        // handing the work over to the renderer, this is because a great deal of
        // data is interaction dependent
        #region Vector Panel

        private void vectorPanel_Paint(object sender, PaintEventArgs e)
        {
            //Bitmap edgesBmp = scene.Painting.ActiveLayer.EdgesImage.Bitmap;
            List<KDNodeData> vectorsList;
            KDNodeData selectedVector = scene.Painting.ActiveLayer.SelectedVector;
            int[,] objectLabels = scene.Painting.ActiveLayer.ObjectLabels;
            
            vectorPanel.SuspendLayout();

            Graphics g = e.Graphics;
            int row, col;
            float x = 0.0f;
            float y = 0.0f;
            float halfInnerCircleRadius = innerCircleRadius / 2.0f;
            float halfOuterCircleRadius = outerCircleRadius / 2.0f;
            Pen pen;
            double angle;

            g.DrawImage(scene.Painting.ActiveLayer.ColorImage.Bitmap, colorPanelRect, highlightRectangleFl, GraphicsUnit.Pixel);
            g.DrawImage(scene.Painting.ActiveLayer.OverlayImage, colorPanelRect, highlightRectangleFl, GraphicsUnit.Pixel);

            // Iterate through all image segments
            foreach (Segment seg in scene.Painting.ActiveLayer.segments)
            {
                foreach (KDNodeData currNode in seg.vectorsList)
                {
                    if (highlightRectangleFl.Contains(currNode.point))
                    {
                        angle = currNode.angle / 255.0 * Math.PI;
                        row = currNode.point.X - highlightRectangleFl.X;
                        col = currNode.point.Y - highlightRectangleFl.Y;

                        x = (float)(Math.Cos(angle) * VECTORLENGTH);
                        y = (float)(Math.Sin(angle) * VECTORLENGTH) * -1.0f;

                        if (selectedVector == currNode)
                        {
                            pen = selectedPen;
                        }
                        else
                        {
                            pen = unselectedPen;
                        }

                        g.DrawLine(pen, row - x, col - y, row + x, col + y);
                        g.DrawEllipse(pen, row - outerCircleRadius / 2, col - outerCircleRadius / 2, outerCircleRadius, outerCircleRadius);
                    }
                }
            }

            // To Do
            //  - Finish vector preview
            //  - Add painting preview
            //  - Start to generate sample images
            //  - Work on it all of today

            // For each element in vectorGrid - draw the corresponding line
            int i = 0;
            int j = 0;

            for (col = 0; col < highlightRectangleFl.Height; col += distance, j++)
            {
                i = 0;
                for (row = 0; row < highlightRectangleFl.Width; row += distance, i++)
                {
                    angle = vectorGrid[i, j] / 255.0 * Math.PI;
                    x = (float)(Math.Cos(angle) * lineLength);
                    y = (float)(Math.Sin(angle) * lineLength) * -1.0f;
                    g.DrawLine(vectorPen, row - x, col - y, row + x, col + y);
                }
            }


            vectorPanel.ResumeLayout();
        }

        private void vectorPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (vectorTool == VectorTool.Add)
            {
                // Store the current mouseDown point
                mouseDownPt = new Point(e.X + highlightRectangleFl.X, e.Y + highlightRectangleFl.Y);

                int segIndex = scene.Painting.ActiveLayer.ObjectLabels[mouseDownPt.X, mouseDownPt.Y];
                if (segIndex < 0)
                {
                    return;
                }
                Segment seg = (Segment)scene.Painting.ActiveLayer.segments[segIndex];
                
                // Generate the KDNode data to store
                KDNodeData newKDNode = new KDNodeData(mouseDownPt, 0.0, 1.0);

                seg.vectorsList.Add(newKDNode);
                scene.Painting.ActiveLayer.SelectedVector = newKDNode;

                isDragging = true;

                //Rectangle invalidateRegion = new Rectangle((int)(e.X - 2 * newKDNode.magnitude),
                //        (int)(e.Y - 2 * newKDNode.magnitude),
                //        (int)(newKDNode.magnitude * 4),
                //        (int)(newKDNode.magnitude * 4));
                vectorPanel.Invalidate();//invalidateRegion);
            }
            else if (vectorTool == VectorTool.Select)
            {
                double[] dLoc = new double[2] { e.X + highlightRectangleFl.X, e.Y + highlightRectangleFl.Y };
                
                int segIndex = scene.Painting.ActiveLayer.ObjectLabels[(int) dLoc[0], (int)dLoc[1]];
                if(segIndex < 0)
                {
                    return;
                }
                Segment seg = (Segment)scene.Painting.ActiveLayer.segments[segIndex];
                if (seg.vectorsKDTree.Count == 0)
                {
                    return;
                }

                KDNodeData newSelection = (KDNodeData)seg.vectorsKDTree.Nearest(dLoc);
                scene.Painting.ActiveLayer.SelectedVector = newSelection;

                vectorPanel.Invalidate();
            }
        }

        private void vectorPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (vectorTool == VectorTool.Add)
            {
                if (!isDragging || !vectorPanel.Focused || !vectorPanel.ClientRectangle.Contains(new Point(e.X, e.Y)))
                {
                    return;
                }

                Point mouseDownPtLocal = new Point(mouseDownPt.X - highlightRectangleFl.X,
                    mouseDownPt.Y - highlightRectangleFl.Y);

                int segIndex = scene.Painting.ActiveLayer.ObjectLabels[mouseDownPtLocal.X, mouseDownPtLocal.Y];
                if(segIndex < 0)
                {
                    return;
                }
                Segment seg = (Segment)scene.Painting.ActiveLayer.segments[segIndex];

                List<KDNodeData> vectorsList = seg.vectorsList;
                KDNodeData selectedVector = scene.Painting.ActiveLayer.SelectedVector;

                // angle = [-.5, .5]
                double angle = Math.Atan2(mouseDownPtLocal.Y - e.Y,
                    e.X - mouseDownPtLocal.X) / (2 * Math.PI);
                // angle = [0, 1]
                if (angle < 0.0)
                {
                    angle += 1.0;
                }
                if (angle >= 0.5)
                {
                    angle -= 0.5;
                }
                // angle = [0, 255];
                angle *= 255 * 2;
                selectedVector.angle = angle;

                selectedVector.magnitude = Math.Sqrt(
                        (e.X - mouseDownPtLocal.X) * (e.X - mouseDownPtLocal.X) +
                        (e.Y - mouseDownPtLocal.Y) * (e.Y - mouseDownPtLocal.Y));

                Point clearPoint = new Point(
                        (int)((float)mouseDownPtLocal.X - VECTORLENGTH * 1.0f),
                        (int)((float)mouseDownPtLocal.Y - VECTORLENGTH * 1.0f));
                Size size = new Size((int)(VECTORLENGTH * 2), (int)(VECTORLENGTH * 2));

                Rectangle invalidateRegion = new Rectangle(clearPoint, size);
                vectorPanel.Invalidate(invalidateRegion);
            }
        }

        private void vectorPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (vectorTool == VectorTool.Add)
            {
                if (!isDragging)
                {
                    return;
                }
                isDragging = false;

                Point mouseDownPtLocal = new Point(mouseDownPt.X - highlightRectangleFl.X,
                   mouseDownPt.Y - highlightRectangleFl.Y);

                // Determine the current segment
                int segIndex = scene.Painting.ActiveLayer.ObjectLabels[mouseDownPt.X, mouseDownPt.Y];
                if (segIndex < 0)
                {
                    return;
                }
                Segment seg = (Segment)scene.Painting.ActiveLayer.segments[segIndex];

                List<KDNodeData> vectorsList = seg.vectorsList;
                KDTreeDLL.KDTree vectorsKDTree = seg.vectorsKDTree;
                KDNodeData selectedVector = scene.Painting.ActiveLayer.SelectedVector;

                // angle = [-.5, .5]
                double angle = Math.Atan2(mouseDownPtLocal.Y - e.Y, e.X - mouseDownPtLocal.X) / (2 * Math.PI);
                // angle = [0, 1]
                if (angle < 0.0)
                {
                    angle += 1.0;
                }
                if (angle >= 0.5)
                {
                    angle -= 0.5;
                }
                // angle = [0, 255];
                angle *= 255 * 2;

                double[] dLoc = new double[2] { (double)mouseDownPt.X, (double)mouseDownPt.Y };
                selectedVector.point = new Point(mouseDownPt.X, mouseDownPt.Y);
                selectedVector.angle = angle;

                seg.vectorsKDTree.Insert(dLoc, selectedVector);

                vectorsDirty = true;
                idleTime = 0;

                vectorPanel.Invalidate();
            }
        }

        private void addVectorToolstripBtn_Click(object sender, EventArgs e)
        {
            vectorPanel.Cursor = Cursors.Cross;
            vectorTool = VectorTool.Add;
        }

        private void selectVectorToolstripBtn_Click(object sender, EventArgs e)
        {
            vectorPanel.Cursor = Cursors.Default;
            vectorTool = VectorTool.Select;
        }

        private void vectorPanel_MouseEnter(object sender, EventArgs e)
        {
            vectorPanel.Focus();
        }

        /// <summary>
        /// Computes the vector field for the preview window
        /// </summary>
        private unsafe void ComputeVectorField()
        {
            RadialBasisInterp radialBasisInterp = new RadialBasisInterp();
            int vectorGridDim = vectorPanel.Width / distance + 1;
            vectorGrid = new byte[vectorGridDim, vectorGridDim];

            double[] dLoc = new double[2] { 0.0, 0.0 };
            Object[] nodeList;
            byte currValue;
            double power = 3.0;
            int i = 0;
            int j = 0;
            KDTreeDLL.KDTree vectorsKDTree;

            // for each line
            for (int y = highlightRectangleFl.Y; y < highlightRectangleFl.Y + highlightRectangleFl.Height; y += distance, j++)
            {
                // for each pixel
                i = 0;
                for (int x = highlightRectangleFl.X; x < highlightRectangleFl.X + highlightRectangleFl.Width; x += distance, i++)
                {
                    // Determine the current segment
                    int segIndex = scene.Painting.ActiveLayer.ObjectLabels[x, y];

                    if (segIndex < 0)
                    {
                        vectorGrid[i, j] = 0;
                        continue;
                    }

                    Segment seg = (Segment)scene.Painting.ActiveLayer.segments[segIndex];

                    if (seg.vectorsKDTree.Count == 0)
                    {
                        vectorGrid[i, j] = 0;
                    }
                    else
                    {
                        vectorGrid[i, j] = radialBasisInterp.InterpAngle(seg.vectorsKDTree, new Point(x, y));
                    }
                }
            }
        }

        private double Distance(Point point, int x, int y)
        {
            return Math.Sqrt((point.X - x) * (point.X - x) + (point.Y - y) * (point.Y - y));
        }

        #endregion

        // Preview of the final painting
        #region Paint Panel

        // Repaint the preview of the final painting
        private void paintPanel_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.CompositingMode = CompositingMode.SourceOver;
            g.CompositingQuality = CompositingQuality.HighSpeed;
            Rectangle previewRect = new Rectangle(0, 0, paintPreview.Width, paintPreview.Height);
            g.DrawImage(paintPreview, previewRect);

            // draw the selected stroke and manipulators
            if(scene.Painting.ActiveLayer.SelectedStroke != null)
            {
                Stroke selectedStroke = scene.Painting.ActiveLayer.SelectedStroke;
                Bitmap colorBrush = scene.Painting.Brush.ColorBrushes[selectedStroke.BrushIndex].GetImage(selectedStroke.Width).Bitmap;
                AForge.Imaging.Filters.ResizeBilinear resizeBilinear = new AForge.Imaging.Filters.ResizeBilinear(selectedStroke.Width, selectedStroke.Height);
                colorBrush = resizeBilinear.Apply(colorBrush);
                RotateBilinearAndColor rotationFilterAndColor = new RotateBilinearAndColor(selectedStroke.Angle, selectedStroke.BaseColor);
                rotationFilterAndColor.FillColor = Color.FromArgb(0, 0, 0, 0);
                colorBrush = rotationFilterAndColor.Apply(colorBrush);
                int drawXPos = selectedStroke.Loc.X - colorBrush.Width / 2;
                int drawYPos = selectedStroke.Loc.Y - colorBrush.Height / 2;
                g.DrawImage(colorBrush, drawXPos - highlightRectangleFl.Left, drawYPos - highlightRectangleFl.Top, colorBrush.Width, colorBrush.Height);

                int row = selectedStroke.Loc.X - highlightRectangleFl.X;
                int col = selectedStroke.Loc.Y - highlightRectangleFl.Y;
                
                float angleRad = (float)(selectedStroke.Angle * Math.PI / 180.0f);
                float x = (float)(Math.Cos(angleRad) * selectedStroke.Width / 2.5f);
                float y = (float)(Math.Sin(angleRad) * selectedStroke.Width / 2.5f) * -1.0f;
                
                g.DrawLine(selectedPen, row - x, col - y, row + x, col + y);
                g.DrawEllipse(selectedPen, row - outerCircleRadius, col - outerCircleRadius, outerCircleRadius * 2, outerCircleRadius * 2);
                g.DrawEllipse(selectedPen, row + x - outerCircleRadius, col + y - outerCircleRadius, outerCircleRadius * 2, outerCircleRadius * 2);
                g.DrawEllipse(selectedPen, row - x - outerCircleRadius, col - y - outerCircleRadius, outerCircleRadius * 2, outerCircleRadius * 2);
            }

            g.Flush();
            //paintPanel.ResumeLayout();
        }

        public void PaintPreview()
        {
            if (scene.Painting.IsRandom)
            {
                scene.Renderer.Painter.PaintRandomNew(paintPreview, null, highlightRectangleFl, false);
            }
            else
            {
                scene.Renderer.Painter.PaintSaved(paintPreview, null, highlightRectangleFl, false);
            }
        }

        private void showBordersBtn_Click(object sender, EventArgs e)
        {
            showBordersBtn.Checked = !showBordersBtn.Checked;
            showBorders = showBordersBtn.Checked;
            paintPanel.Invalidate();
        }

        private void paintPanel_MouseDown(object sender, MouseEventArgs e)
        {
            // See if we are clicking within the movement circles
            Stroke selectedStroke = scene.Painting.ActiveLayer.SelectedStroke;
            if (selectedStroke != null)
            {
                float x = (float)(selectedStroke.Loc.X - highlightRectangleFl.X + (Math.Cos(selectedStroke.Angle * Math.PI / 180.0f) * selectedStroke.Width / 2.5f));
                float y = (float)(selectedStroke.Loc.Y - highlightRectangleFl.Y + (Math.Sin(selectedStroke.Angle * Math.PI / 180.0f) * selectedStroke.Width / 2.5f) * -1.0f);
                if (Math.Sqrt((e.X - x) * (e.X - x) + (e.Y - y) * (e.Y - y)) < outerCircleRadius)
                {
                    isDragging = true;
                    strokeMode = StrokeMode.RotateR;
                    return;
                }
                x = (float)(selectedStroke.Loc.X - highlightRectangleFl.X - (Math.Cos(selectedStroke.Angle * Math.PI / 180.0f) * selectedStroke.Width / 2.5f));
                y = (float)(selectedStroke.Loc.Y - highlightRectangleFl.Y - (Math.Sin(selectedStroke.Angle * Math.PI / 180.0f) * selectedStroke.Width / 2.5f) * -1.0f);
                if (Math.Sqrt((e.X - x) * (e.X - x) + (e.Y - y) * (e.Y - y)) < outerCircleRadius)
                {
                    isDragging = true;
                    strokeMode = StrokeMode.RotateL;
                    return;
                }
                x = (float)(selectedStroke.Loc.X - highlightRectangleFl.X);
                y = (float)(selectedStroke.Loc.Y - highlightRectangleFl.Y);
                if (Math.Sqrt((e.X - x) * (e.X - x) + (e.Y - y) * (e.Y - y)) < outerCircleRadius)
                {
                    isDragging = true;
                    strokeMode = StrokeMode.Translate;
                    return;
                }
            }
            scene.Painting.ActiveLayer.SelectedStroke =
                scene.Painting.ActiveLayer.StrokeArray[e.X + highlightRectangleFl.X, e.Y + highlightRectangleFl.Y];
            paintPanel.Invalidate();
            mainForm.strokeProperties.InitStroke();
        }

        private void paintPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (!isDragging || scene.Painting.ActiveLayer.SelectedStroke == null)
            {
                return;
            }

            // local reference to selectedStroke
            Stroke selectedStroke = scene.Painting.ActiveLayer.SelectedStroke;
            int localX = selectedStroke.Loc.X - highlightRectangleFl.X;
            int localY = selectedStroke.Loc.Y - highlightRectangleFl.Y;

            if (strokeMode == StrokeMode.RotateL || strokeMode == StrokeMode.RotateR)
            {
                // angle = [-Pi, Pi]
                double angle = Math.Atan2(localY - e.Y,
                    e.X - localX) * 180.0f / Math.PI;
                // reverse if left handle selected
                if (strokeMode == StrokeMode.RotateL)
                {
                    angle += Math.PI;
                }
                if (angle < 0.0)
                {
                    angle += Math.PI;
                }
                // angle = [0, 255];
                selectedStroke.Angle = (float)angle;
                Bitmap colorBrush = scene.Painting.Brush.ColorBrushes[0].GetImage(selectedStroke.Width).Bitmap;
                float aspectRatio = (float)colorBrush.Height / colorBrush.Width;
                selectedStroke.Width = (int)Math.Sqrt((e.X - localX) * (e.X - localX) +
                    (e.Y - localY) * (e.Y - localY)) * 2;
                if (selectedStroke.Width < 5)
                {
                    selectedStroke.Width = 5;
                }
                selectedStroke.Height = (int)(selectedStroke.Width * aspectRatio);

                Point clearPoint = new Point(localX - selectedStroke.Width / 2,
                    localY - selectedStroke.Width / 2);
                Size size = new Size((int)(selectedStroke.Width + 10), (int)(selectedStroke.Width + 10));
                Rectangle invalidateRegion = new Rectangle(clearPoint, size);
                paintPanel.Invalidate(invalidateRegion);
            }
            else //StrokeMode.Translate
            {
                selectedStroke.Loc.X = e.X + highlightRectangleFl.X;
                selectedStroke.Loc.Y = e.Y + highlightRectangleFl.Y;

                Point clearPoint = new Point((int)(e.X - selectedStroke.Width / 1.5),
                    (int)(e.Y - selectedStroke.Width / 1.5));
                Size size = new Size((int)(selectedStroke.Width * 1.25 + 10), (int)(selectedStroke.Width * 1.25 + 10));
                Rectangle invalidateRegion = new Rectangle(clearPoint, size);
                paintPanel.Invalidate(invalidateRegion);
            }
        }

        private void paintPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (isDragging)
            {
                PaintPreview();
            }
            isDragging = false;
            strokeMode = StrokeMode.None;
            paintPanel.Invalidate();
        }

        private void randomToolStripButton_Click(object sender, EventArgs e)
        {
            scene.Painting.IsRandom = !scene.Painting.IsRandom;
        }

        private void paintPanel_MouseEnter(object sender, EventArgs e)
        {
            paintPanel.Focus();
        }
        #endregion

        private void idleTimer_Tick(object sender, EventArgs e)
        {
            if (vectorsDirty)
            {
                idleTime += idleTimer.Interval;
                if (idleTime > IDLETIMEOUT)
                {
                    ComputeVectorField();
                    vectorPanel.Invalidate();
                    PaintPreview();
                    paintPanel.Invalidate();
                    vectorsDirty = false;
                }
            }
        }


    }
}
